Imports System
Imports System.ComponentModel.Composition
Imports Contracts
Imports System.Reflection
Imports Microsoft.WindowsAPICodePack
Imports Microsoft.WindowsAPICodePack.Sensors
Imports System.ComponentModel
Imports System.Threading
Imports System.Collections.ObjectModel


<WpfServiceMetadata("SeismoAddin")> _
Public Class SeismoAddinImpl
	Implements IWpfService
	Implements INotifyPropertyChanged

	Public Event StatusUpdated As EventHandler Implements IWpfService.StatusUpdated
	Private _status As String

	Private ReadOnly PerfCategoryName As String = "Sensors"
	Private ReadOnly PerfNetForceName As String = "Accelerometer - Magnitude"
	Private _category As PerformanceCounterCategory
	Private _perfCounter As PerformanceCounter

	Private t As Thread
	Private _ReadingsX As ObservableCollection(Of SensorReading) = New ObservableCollection(Of SensorReading)()
	Private _ReadingsY As ObservableCollection(Of SensorReading) = New ObservableCollection(Of SensorReading)()
	Private _ReadingsZ As ObservableCollection(Of SensorReading) = New ObservableCollection(Of SensorReading)()

	Private WithEvents _accelSensor As Accelerometer3D

	Private _optionsControl As New SeismoUserControl()

	Public Sub New()
		Console.WriteLine("Seismometer tracker starting up!")
	End Sub

#Region "Basic addin stuff"
	Public ReadOnly Property OptionsUserControl() As System.Windows.Controls.UserControl Implements IWpfService.OptionsUserControl
		Get
			Return _optionsControl
		End Get
	End Property

	Public ReadOnly Property Name() As String Implements IWpfService.Name
		Get
			Return "Sensor Seismometer"
		End Get
	End Property

	Public ReadOnly Property AddinIconUri() As Uri Implements IWpfService.AddinIconUri
		Get
			Dim u As String = CreateResourceUri("chart.png")

			Return New Uri(u)
		End Get
	End Property

	Private Function CreateResourceUri(ByVal name As String) As String
		Dim a As Assembly = Assembly.GetExecutingAssembly()
		Return String.Format("pack://application:,,,/{0}component/Resources/{1}", a.GetName().Name, name)
	End Function

	Public ReadOnly Property Description() As String Implements IWpfService.Description
		Get
			Return "Writes sensor values to a performance counter and can sound an alarm"
		End Get
	End Property

	Public ReadOnly Property Author() As String Implements IWpfService.Author
		Get
			Return "Arian T. Kulp"
		End Get
	End Property

	Public ReadOnly Property Version() As Version Implements IWpfService.Version
		Get
			Return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version
		End Get
	End Property

	Public ReadOnly Property AuthorUri() As Uri Implements IWpfService.AuthorUri
		Get
			Return New Uri("http://www.ariankulp.com/")
		End Get
	End Property

	Public ReadOnly Property UpdateUri() As Uri Implements IWpfService.UpdateUri
		Get
			Return New Uri("http://www.ariankulp.com/downloads/")
		End Get
	End Property

	Public Property Status() As String Implements IWpfService.Status
		Get
			Return _status
		End Get
		Set(ByVal value As String)
			If value <> _status Then
				_status = value

				OnPropertyChanged("Status")

				RaiseEvent StatusUpdated(Me, Nothing)
			End If
		End Set
	End Property

	Public Sub Initialize() Implements IWpfService.Initialize
		_optionsControl.DataContext = Me
	End Sub
#End Region

	Public Sub Start() Implements IWpfService.Start

		If Not OperatingSystemUtil.MeetsOrExceedsWin7() Then
			' Fatal error
			Throw New UtilityRuntimeException("This addin requires Windows 7 or higher to operate")
		Else
			CreatePerfCounter()
			LocateSensors()
			Me.Sensitivity = 1.5
		End If

		Status = "Seismometer sensor started"
	End Sub

	Public Sub [Stop]() Implements IWpfService.Stop
		_accelSensor = Nothing
		Status = "Seismometer stopped"
	End Sub

#Region "Public properties"

#Region "Magnitude Property"
	Private _magnitude As Double
	Public Property Magnitude() As Double
		Get
			Return _magnitude
		End Get
		Set(ByVal value As Double)
			_magnitude = value
			OnPropertyChanged("Magnitude")
		End Set
	End Property
#End Region

#Region "LastDataReportX Property"
	Private _lastDataReportX As Decimal

	Public Property LastDataReportX() As Decimal
		Get
			Return _lastDataReportX
		End Get
		Set(ByVal value As Decimal)
			_lastDataReportX = value
			OnPropertyChanged("LastDataReportX")
		End Set
	End Property
#End Region

#Region "LastDataReportY Property"
	Private _lastDataReportY As Decimal

	Public Property LastDataReportY() As Decimal
		Get
			Return _lastDataReportY
		End Get
		Set(ByVal value As Decimal)
			_lastDataReportY = value
			OnPropertyChanged("LastDataReportY")
		End Set
	End Property
#End Region

#Region "LastDataReportZ Property"
	Private _lastDataReportZ As Decimal

	Public Property LastDataReportZ() As Decimal
		Get
			Return _lastDataReportZ
		End Get
		Set(ByVal value As Decimal)
			_lastDataReportZ = value
			OnPropertyChanged("LastDataReportZ")
		End Set
	End Property
#End Region

#Region "Sensitivity Property"
	Private _sensitivity As Double
	Public Property Sensitivity() As Double
		Get
			Return _sensitivity
		End Get
		Set(ByVal value As Double)
			_sensitivity = value
			OnPropertyChanged("Sensitivity")
		End Set
	End Property
#End Region

#Region "SensorName Property"
	Private _sensorName As String
	Public Property SensorName() As String
		Get
			Return _sensorName
		End Get
		Set(ByVal value As String)
			_sensorName = value
			OnPropertyChanged("SensorName")
		End Set
	End Property
#End Region

#Region "ReadingsX Property"
	Public Property ReadingsX() As ObservableCollection(Of SensorReading)
		Get
			Return _ReadingsX
		End Get
		Private Set(ByVal value As ObservableCollection(Of SensorReading))
			_ReadingsX = value
		End Set
	End Property
#End Region

#Region "ReadingsY Property"
	Public Property ReadingsY() As ObservableCollection(Of SensorReading)
		Get
			Return _ReadingsY
		End Get
		Private Set(ByVal value As ObservableCollection(Of SensorReading))
			_ReadingsY = value
		End Set
	End Property
#End Region

#Region "ReadingsZ Property"
	Public Property ReadingsZ() As ObservableCollection(Of SensorReading)
		Get
			Return _ReadingsZ
		End Get
		Private Set(ByVal value As ObservableCollection(Of SensorReading))
			_ReadingsZ = value
		End Set
	End Property
#End Region

#End Region

	Sub SensorManagerSensorsChanged(ByVal e As SensorsChangedEventArgs)
		If e.Change = SensorAvailabilityChange.Removal Then
				_accelSensor = Nothing
				LastDataReportX = 0
				LastDataReportY = 0
				LastDataReportZ = 0
				Magnitude = 0
				Status = "Sensor disconnected"
				ReadingsX.Clear()
				ReadingsY.Clear()
				ReadingsZ.Clear()
		ElseIf e.Change = SensorAvailabilityChange.Addition Then

			' s will be null if new sensor is not the right type
			Dim s As Sensor
			s = SensorManager.GetSensorBySensorId(Of Accelerometer3D)(e.SensorId)
			If s IsNot Nothing Then
				PrepareSensor(s)
			End If
			Status = "Sensor added"
		End If
	End Sub

	Private Sub PrepareSensor(ByVal sensor As Sensor)
		_accelSensor = sensor

		If sensor.State = SensorState.Ready Then
			sensor.AutoUpdateDataReport = False
			Me.SensorName = sensor.Manufacturer & " " & sensor.Model & " (" & sensor.FriendlyName & ")"

			t = New Thread(AddressOf ThreadBody)
			t.IsBackground = True
			t.Start()
		End If
	End Sub

	Private Sub ThreadBody()
		While _accelSensor IsNot Nothing
			UpdateData()
			Thread.Sleep(250)
		End While
	End Sub

#Region "INotifyPropertyChanged Members"

	Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

	Private Sub OnPropertyChanged(ByVal prop As String)
		RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
	End Sub
#End Region

Private Sub CreatePerfCounter()
	If (PerformanceCounterCategory.Exists(PerfCategoryName)) Then
		PerformanceCounterCategory.Delete(PerfCategoryName)
	End If

	' Create a collection of type CounterCreationDataCollection.
	Dim CounterCreationData As New CounterCreationDataCollection()

	' Create the counter, set properties, and add to collection
	Dim ccd As New CounterCreationData()
	With (ccd)
		.CounterName = PerfNetForceName
		.CounterHelp = "The net acceleration value (in G's)"
		.CounterType = PerformanceCounterType.NumberOfItems64
	End With

	CounterCreationData.Add(ccd)

	' Create the category and pass the collection to it.
	_category = PerformanceCounterCategory.Create(PerfCategoryName, _
		"The Sensors performance object tracks the value of various sensors over time", _
		PerformanceCounterCategoryType.SingleInstance, CounterCreationData)

	_perfCounter = New PerformanceCounter(PerfCategoryName, PerfNetForceName, False)
End Sub

	Private Sub LocateSensors()
		Try

			Dim sensors As SensorList(Of Accelerometer3D) = SensorManager.GetSensorsByTypeId(Of Accelerometer3D)()

			If sensors Is Nothing Then
				Status = "No sensors found"
			Else

				Status = "Found " & sensors.Count & " sensors"

				If sensors.Count > 0 Then
					Dim s2 As SensorList(Of Sensor) = New SensorList(Of Sensor)()
					s2.Add(sensors(0))
					SensorManager.RequestPermission(IntPtr.Zero, False, s2)
					PrepareSensor(sensors(0))
				End If

			End If

		Catch x As SensorPlatformException
			Status = x.Message
		End Try
	End Sub

Private Sub UpdateData()
	_optionsControl.Dispatcher.BeginInvoke(Sub() UpdateDataInvoked())
End Sub

Private Sub UpdateDataInvoked()
	If (_accelSensor Is Nothing) OrElse (_accelSensor.TryUpdateData() = False) Then
		Return
	End If

	Dim currentAccel = _accelSensor.CurrentAcceleration
	Dim sampleTime As DateTime = DateTime.Now

	Me.LastDataReportX = currentAccel(AccelerationAxis.X)
	Me.LastDataReportY = currentAccel(AccelerationAxis.Y)
	Me.LastDataReportZ = currentAccel(AccelerationAxis.Z)

	Me.Magnitude = Math.Sqrt(Math.Pow(Me.LastDataReportX, 2) + Math.Pow(Me.LastDataReportY, 2) + Math.Pow(Me.LastDataReportZ, 2))

	If _perfCounter IsNot Nothing Then
		_perfCounter.RawValue = Convert.ToInt64(Me.Magnitude * 10)
	End If

	ReadingsX.Add(New SensorReading() With { _
		.Reading = Me.LastDataReportX, _
		.Timestamp = sampleTime _
	})
	ReadingsY.Add(New SensorReading() With { _
		.Reading = Me.LastDataReportY, _
		.Timestamp = sampleTime _
	})
	ReadingsZ.Add(New SensorReading() With { _
		.Reading = Me.LastDataReportZ, _
		.Timestamp = sampleTime _
	})

	If ReadingsX.Count > 20 Then
		ReadingsX.RemoveAt(0)
	End If
	If ReadingsY.Count > 20 Then
		ReadingsY.RemoveAt(0)
	End If
	If ReadingsZ.Count > 20 Then
		ReadingsZ.RemoveAt(0)
	End If

	If Me.Magnitude > Me.Sensitivity Then
		Status = String.Format("Big movement! ({0} G's)", Magnitude)
		System.Media.SystemSounds.Exclamation.Play()
	End If
End Sub

	Private Sub SensorStateChanged(ByVal sender As Object, ByVal e As EventArgs) Handles _accelSensor.StateChanged
		PrepareSensor(sender)
	End Sub

End Class
